// Type Definition
class ProtocolInfo {
    apk_id: string = "com.tencent.mobileqq"
    version: string = "9.0.17"
    app_id: number | undefined
    sub_app_id: number | undefined
    app_key: string | undefined
    sort_version_name: string | undefined
    build_time: number | undefined
    apk_sign: string = "a6b745bf24a2c277527716f6f36eb68d"
    sdk_version: string | undefined
    sso_version: number | undefined
    misc_bitmap: number | undefined
    main_sig_map: number | undefined
    sub_sig_map: number | undefined
    dump_time: string = new Date().getTime().toString()
    qua: string | undefined
    protocol_type: number = 1
}

class DeviceInfo {
    virtualRootPath: string = "virtualRoot"
    appInstallFolder: string = "/data/app/~~nNzv5koU9DgkrbtCpa02wQ==/${packageName}-fR9VqAFGIZNVZ8MgZYh0Ow=="
    screenSizeWidth: number = 1080
    screenSizeHeight: number = 2400
    density: string = "2.75"
    serialNumber: string = "0x0000043be8571339"
    androidVersion: string = "13"
    androidSdkVersion: number = 33
    targetSdkVersion: number = 29
    storageSize: string = "137438953471"
    deviceSign: { [key: string]: string } = {}
}

class DtConfig {
    en: Array<Array<number>> = []
    de: Array<Array<number>> = []
}

class Result {
    deviceInfo: DeviceInfo = new DeviceInfo()
    protocol: ProtocolInfo = new ProtocolInfo()
    dtConfig: DtConfig = new DtConfig()
}

// Hook Tencent QQ
Java.perform(function () {
    console.log("[+] Starting to hook")
    console.log("[+] Awaiting QQ's Initialization")
    // get context and load all info

    // valid for fresh run
    let context: any = null
    let contextMethod = Java.use("com.tencent.common.app.BaseApplicationImpl")["attachBaseContext"]
    contextMethod.implementation = function (inComingcontext: any) {
        this.attachBaseContext(inComingcontext)
        hookQsignInfo(inComingcontext).then((result) => {
            console.log("[+] Done")
        })
        // hookTxProtocol(inComingcontext).then((result) => {
        //     console.log("[+] Done")
        // })
    }

    // valid for already running
    Java.choose("com.tencent.mobileqq.app.QQAppInterface", {
        onMatch: function (instance) {
            console.log("[+] QQAppInterface Found")
            let context = instance.getApplication().getApplicationContext()
            hookQsignInfo(context).then((result) => {
                 console.log("[+] Done")
             })
            //hookTxProtocol(context).then((result) => {
            //    console.log("[+] Done")
            //})
        },
        onComplete: function () {
            console.log("[+] Completed")
        }
    })
})

function hookTxProtocol(context: any): Promise<void> {
    // 干掉 QQ Safe
    let cryptor = Java.use("oicq.wlogin_sdk.tools.cryptor")!!
    // let fromService = Java.use("com.tencent.qphone.base.remote.FromServiceMsg")!!
    let EcdhCrypt = Java.use("oicq.wlogin_sdk.tools.EcdhCrypt")
    let Ticket = Java.use("oicq.wlogin_sdk.request.Ticket")
    let util = Java.use("oicq.wlogin_sdk.tools.util")
    let oicq_request = Java.use("oicq.wlogin_sdk.request.oicq_request")!!
    let WtloginHelper = Java.use("oicq.wlogin_sdk.request.WtloginHelper")!!
    let tlv_t = Java.use("oicq.wlogin_sdk.tlv_type.tlv_t")
    let MD5 = Java.use("oicq.wlogin_sdk.tools.MD5")
    let TcpProtocolDataCodec = Java.use("com.tencent.mobileqq.highway.codec.TcpProtocolDataCodec")
    hookReceive(context)
    hookSend(context)
    

    return Promise.resolve()
}

function hookSend(context: any) {
    let MSFServlet = Java.use("mqq.app.MSFServlet");
    MSFServlet["sendToMSF"].implementation = function (_: any, obj: any) {
        let seq = obj.getRequestSsoSeq() as number
        let cmd = obj.getServiceCmd() as string
        console.log(`[↑] [MSFServlet.sendToMSF] seq=${seq}, cmd=${cmd}`)
        let uin = obj.getUin() as string
        let wupBuffer = obj.getWupBuffer() as Array<number>
        let quickSendStrategy = obj.getQuickSendStrategy() as number
        console.log(` |-> uin=${uin}, type=unknown, quickSendStrategy=${quickSendStrategy}, buffer=${JSON.stringify(wupBuffer)}`)
        console.log()
        };
}

function hookReceive(context: any) {
    let MSFServlet = Java.use("mqq.app.MSFServlet");
    MSFServlet["onReceive"].overload('com.tencent.qphone.base.remote.FromServiceMsg').implementation = function (obj: any) {
        let seq = obj.getRequestSsoSeq() as number
        let cmd = obj.getServiceCmd() as string
        console.log(`[↓] [MSFServlet.onReceive] seq=${seq}, cmd=${cmd}`)
        let cookie = obj.getMsgCookie() as Array<number>
        let uin = obj.getUin() as string
        let buffer = obj.getWupBuffer() as Array<number>
        let enc = obj.getSsoEnc() as number
        console.log(` |-> uin=${uin}, type=unknown, msgCookie=${JSON.stringify(cookie)}, enc=${enc}, buffer=${JSON.stringify(buffer)}`)
        console.log()
        let result = this["onReceive"](obj);
        return result;
    };
}


function printStacktrace() {
    Java.perform(() => {
        Java.use("java.lang.Thread").currentThread().getStackTrace().forEach((element: any) => {
            console.log(element.toString());
        });
    });
}


// qsign

function hookQsignInfo(context: any): Promise<Result> {
    let protocol = LoadProtocolInfo(context)
    let dtConfig = LoadDtConfig(context)
    let deviceInfo = LoadDeviceInfo(context)
    console.log("===== Load Done =====")
    console.log("====== qsign.json =====")
    console.log(JSON.stringify(deviceInfo, null, 4))
    console.log("====== " +protocol.version + "/dtconfig.json =====")
    console.log(JSON.stringify(dtConfig))
    let padProtocol = JSON.parse(JSON.stringify(protocol))
    padProtocol.app_id = protocol.sub_app_id
    padProtocol.protocol_type = 6
    protocol.sub_app_id = protocol.app_id
    console.log("====== " +protocol.version + "/android_pad.json =====")
    console.log(JSON.stringify(padProtocol, null, 4))
    console.log("====== " +protocol.version + "/android_phone.json =====")
    console.log(JSON.stringify(protocol, null, 4))
    console.log("===== All Done =====")
    return Promise.resolve({ deviceInfo, protocol, dtConfig })
}

function LoadDeviceInfo(context: any): DeviceInfo {
    console.log("[+] Loading Device Info")
    let deviceInfo = new DeviceInfo()

    // appInstallFolder
    // Get Valueof "java/io/File->getPackageResourcePath()Ljava/lang/String;"
    deviceInfo.appInstallFolder = (<string>context.getPackageResourcePath()).replace(/\/com.*?-/gm, `-\${packageName}-`)
    deviceInfo.appInstallFolder = deviceInfo.appInstallFolder.replace("/base.apk", "")

    let Dtc = Java.use("com.tencent.mobileqq.dt.app.Dtc");
    // enumerate Dtc
    Dtc.$ownMembers.forEach((item) => {
        let member = Dtc[item]
        if (member.value) {
            deviceInfo.deviceSign[member.value as string] = Dtc.mmKVValue(member.value)
        }
    })

    deviceInfo.deviceSign["bootrecord@202407251547"] = Dtc["mmQsecKVValue"]("bootrecord@202407251547")

    let knownSystemProp = ["java.version", "java.vendor", "java.vendor.url", "java.home", "java.vm.specification.version", "java.vm.specification.vendor", "java.vm.specification.name", "java.vm.version", "java.vm.vendor", "java.vm.name", "java.specification.version", "java.specification.vendor", "java.specification.name", "java.class.version", "java.class.path", "java.library.path", "java.io.tmpdir", "java.compiler", "java.ext.dirs", "os.name", "os.arch", "os.version", "file.separator", "path.separator", "line.separator", "user.name", "user.home", "user.dir", "recovery_id"]
    knownSystemProp.forEach((item) => {
        deviceInfo.deviceSign[item] = Dtc.systemGetSafe(item) as string
    })

    deviceInfo.deviceSign["packageUid"] = context.getPackageManager().getPackageUid("com.tencent.mobileqq", 0)

    deviceInfo.screenSizeWidth = context.getResources().getDisplayMetrics().widthPixels.value
    deviceInfo.screenSizeHeight = context.getResources().getDisplayMetrics().heightPixels.value

    deviceInfo.density = context.getResources().getDisplayMetrics().density.value.toString()

    // storage size
    let StatFs = Java.use("android.os.StatFs");
    let path = context.getFilesDir().getAbsolutePath()
    let statFs = StatFs.$new(path)
    let blockSize = statFs.getBlockSize()
    let blockCount = statFs.getBlockCount()
    let totalSize = blockSize * blockCount
    deviceInfo.storageSize = totalSize.toString()

    // serialNumber currently not supported
    return deviceInfo
}

function LoadProtocolInfo(context: any): ProtocolInfo {
    console.log("[+] Loading Protocol Info")
    // 获取协议信息
    let protocol = new ProtocolInfo()

    // AppSetting
    var sAppSetting = Java.use("com.tencent.common.config.AppSetting");
    sAppSetting.$ownMembers.forEach((item) => {
        let value = sAppSetting[item].value
        // shortVersionCode
        if (typeof value === 'string') {
            if (value.startsWith("V")) {
                protocol.short_version_name = value.substring(2)
                protocol.version = value.substring(2, value.lastIndexOf("."))
            }
        }

        // obfuscated class, currently _e and _f
        // try to evaluate fields to get potentially right value
        if (typeof value === 'number') {
            if (value > 500000000) {
                if (typeof protocol.app_id !== 'undefined') {
                    protocol.sub_app_id = value
                } else {
                    protocol.app_id = value
                }
            }
        }
    })

    // WtloginHelper
    let sWtloginHelper = Java.use("oicq.wlogin_sdk.request.WtloginHelper").$new(context)
    protocol.misc_bitmap = <number>sWtloginHelper["mMiscBitmap"].value
    protocol.main_sig_map = <number>sWtloginHelper["mMainSigMap"].value
    protocol.sub_sig_map = <number>sWtloginHelper["mSubSigMap"].value

    // appKey
    let EventType = Java.use("oicq.wlogin_sdk.report.event.EventConstant$EventType");
    let passwordField = <string>EventType["EVENT_WT_LOGIN_PASSWORD"].value
    let appKey = passwordField.replace("_wt_login_password", "")
    protocol.app_key = appKey

    // login.utils
    var sUtil = Java.use("oicq.wlogin_sdk.tools.util");
    protocol.build_time = parseInt(sUtil["BUILD_TIME"].value)
    protocol.sdk_version = <string>sUtil["SDK_VERSION"].value
    protocol.sso_version = <number>sUtil["SSO_VERSION"].value

    // qua
    var sQUA = Java.use("cooperation.qzone.QUA");
    protocol.qua = <string>sQUA["QUA"].value

    // pulling libFekit.so

    

    // get signing
    let SecUtil = Java.use("com.tencent.mobileqq.utils.SecUtil");
    let hash = <string>SecUtil["getSignatureHash"](SecUtil["getSign"](context))
    protocol.apk_sign = hash.toLowerCase()


    return protocol
}

function LoadDtConfig(context: any): DtConfig {
    // dtconfig.json
    let dtConfig = new DtConfig()
    let Gson = Java.use("com.google.gson.Gson").$new()
    let FEBound = Java.use("com.tencent.mobileqq.dt.model.FEBound")
    let deField = FEBound.class.getDeclaredField("mConfigDeCode")
    deField.setAccessible(true)
    let deFieldValue = deField.get(null)
    let enField = FEBound.class.getDeclaredField("mConfigEnCode")
    enField.setAccessible(true)
    let enFieldValue = enField.get(null)
    let deJson = Gson.toJson(deFieldValue)
    let enJson = Gson.toJson(enFieldValue)
    dtConfig.de = JSON.parse(deJson)
    dtConfig.en = JSON.parse(enJson)
    return dtConfig
}